package com.myBlast.server;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.NameValuePair;
import com.myBlast.server.FetchHitData;
import com.myBlast.server.AntHandler;
import com.myBlast.server.Messages;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.myBlast.client.services.MyBlastServiceInterface;
import com.myBlast.data.BlastResult;

/**
 * This is the servlet class for myBlast application
 * @author Parit Bansal
 */
@SuppressWarnings("serial")
public class MyBlastServiceImpl extends RemoteServiceServlet 
implements MyBlastServiceInterface{

	/**
	 * This is an handler over the antHandler utility.
	 * This is initialised in the init method of the servlet.
	 */
	private static AntHandler antHandler = null;
	
	/**
	 * This contains the path to the folder where all
	 * the projects will be saved.  
	 */
	static String ProjectFolder = Messages.getString("outPath_value"); //$NON-NLS-1$
	
	/**
	 * A handle over random number generating object for creating 
	 * csv files to be saved by the user. 
	 */
	public static Random rand = null;
	
	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	@Override
	public void doGet(HttpServletRequest req , HttpServletResponse resp)
	{
		String fileName = null;
		String folder = null;
		String queryStr = req.getQueryString();
		String[] pairs = queryStr.split("&");
		for (String pair : pairs)
		{
			String[] vals = pair.split("=");
			if (vals[0].equalsIgnoreCase("file"))
			{
				fileName = vals[1];
			}
			else if (vals[0].equalsIgnoreCase("folder"))
			{
				folder = vals[1];
			}
		}
		if (fileName == null || folder == null) return;
        try {
        	File f = new File(ProjectFolder + "/" + folder + "/" + fileName);
        	resp.reset();
        	resp.setContentType("application/octet-stream");
        	resp.setContentLength((int)f.length());
        	resp.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
        	int length = 0;
        	ServletOutputStream op = resp.getOutputStream();
        	byte[] bbuf = new byte[1028];
        	DataInputStream in = new DataInputStream(new FileInputStream(f));
        	while ((in != null) && ((length = in.read(bbuf)) != -1))
        	{
        		op.write(bbuf,0,length);
        	}
        	op.flush();
        	op.close();
        }
        catch (IOException e) {
            
            e.printStackTrace();
        }
	}
	
	/* (non-Javadoc)
	 * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
	 */
	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		antHandler = new AntHandler();
		rand = new Random();
	}
	
	/* (non-Javadoc)
	 * @see com.myBlast.client.services.MyBlastServiceInterface#saveFile(java.util.List)
	 */
	public String getCSV(List<BlastResult> rslts)
	{
		// remove the one containing the organism name and project settings
		BlastResult blastResult = rslts.remove(rslts.size() - 1);
		File fl = null;
		// this is noot organism name but the project folder..
		// because am lzy thats why I did it :)
		String folderName = ProjectFolder + "/" + blastResult.getOrganismName();
		fl = new File(folderName);
		if (fl.exists() && fl.isDirectory())
		{
			String[] fileNames = fl.list();
			for (String fileName : fileNames)
			{
				if (fileName.endsWith(".csv"))
				{
					return fileName;
				}
			}
		}
		return "Please save the project first to get the csv file";
	}
	
	public String saveProject(List<BlastResult> rslts)
	{
		ArrayList<BlastResult> rslt = new ArrayList<BlastResult>(rslts);
		BlastResult blastResult = rslts.remove(rslts.size() - 1);
		File fl = null;
		String folderName = ProjectFolder + "/" + blastResult.getOrganismName();
		fl = new File(folderName);
		String fileName = null;
		
		if (fl.exists() && fl.isDirectory())
		{
			String[] fileNames = fl.list();
			for (String name : fileNames)
			{
				if (name.endsWith(".csv") || name.endsWith(".prg"))
				{
					fileName = name.split("[.]")[0];
					break;
				}
			}
			if (fileName == null)
			{
				while(true)
				{
					int file = MyBlastServiceImpl.rand.nextInt();
					fileName = String.valueOf(file);
					fl = new File(folderName + "/" + fileName + ".prg");
					if (file > 0 && !fl.exists())
					{
						break;
					}
				}
			}
		}
		
		boolean success = BlastUtilitiies.writeProject(rslt, folderName + "/" + fileName);
		if (success) return "Project saved successfully";
		else return "Project could not be saved";
	}
	
	/* (non-Javadoc)
	 * @see com.myBlast.client.services.MyBlastServiceInterface#getData(java.lang.String)
	 */
	public String getData(String input) 
	{
		System.out.println(input);
		// data[0] = organism name and data[1] = tax id file path
		String[] data = input.split("---");
		try
		{
			System.out.println("Accessing File" + ProjectFolder + "/" + data[0] + "/"+data[1]+".out");
			File fl = new File(ProjectFolder + "/" + data[0] + "/"+data[1]+".out");
			if (fl.exists()) 
			{
				BufferedReader buff = new BufferedReader(new FileReader(fl));
				StringBuffer strbuff = new StringBuffer("<pre>");
				String readLine = buff.readLine();
				while(readLine != null)
				{
					strbuff.append(readLine+"\n");
					readLine = buff.readLine();
				}
				strbuff.append("</pre>");
				return strbuff.toString();
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
		return null;
	}
	/* (non-Javadoc)
	 * @see com.myBlast.client.services.MyBlastServiceInterface#getResults(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
		@Override
		public List<BlastResult> getResults(String str) {
			ArrayList<BlastResult> hitResults = new ArrayList<BlastResult>();
			//queryWidget.getData() + "--" +organismSelectionWidget.getSelectedTaxids();
			//query+queryType+dbType+programList+expectancy+filterList+projectFolder+newProjectCheck+blastDatabase+megablast
			System.out.println("New Request" + str);
			String[] params = str.split(Messages.getString("BlastServletImpl.1")); //$NON-NLS-1$
			String path = getServletConfig().getServletContext().getRealPath(Messages.getString("BlastServletImpl.2")); //$NON-NLS-1$
			boolean success = true;
			boolean isNewProject = true;
			try 
			{
				String[] parameters = params[0].split(Messages.getString("BlastServletImpl.9"));
				if (parameters[7].equalsIgnoreCase("false")) isNewProject = false;
				if (isNewProject)
				{
					success = createQueryFile(path, success, parameters);
				}
				File fl = new File(Messages.getString("outPath_value") + parameters[6]);
				if (success)
				{
					if (fl.exists() && fl.isDirectory())
					{
						if (isNewProject)
						{
							System.out.println("WorkSpace exists cleaning it "+Messages.getString("outPath_value") + parameters[6]);
							success = clearWorkspace(parameters, fl);
						}
						else
						{
							System.out.println("Workspace found " + Messages.getString("outPath_value") + parameters[6]);
						}
					}
					else
					{
						if (isNewProject)
						{
							try
							{
								System.out.println("creating workspace here " + Messages.getString("outPath_value") + parameters[6]);
								success = fl.mkdir();
								if (success)
								{
									System.out.println("New Workspace created " + Messages.getString("outPath_value") + parameters[6]);
								}
								else
								{
									System.out.println("New Workspace could not be created " + Messages.getString("outPath_value") + parameters[6]);
								}
								
							}
							catch(Exception e)
							{
								success = false;
								System.out.println("New Workspace could not be created " + Messages.getString("outPath_value") + parameters[6]);
								e.printStackTrace();
							}
						}
						else
						{
							BlastResult blastResult = new BlastResult();
							blastResult.setErrorMessage("Couldnt find the workspace");
							System.out.println("Could not find the asked workspace" + Messages.getString("outPath_value") + parameters[6]);
							hitResults.add(blastResult);
							return hitResults;
						}
					}
				}
	
				if (success)
				{
					if (isNewProject)
					{
						String[] taxIds = params[1].split(Messages.getString("BlastServletImpl.9"));
						HashMap<String, String> orgMap = runBlast(path, parameters, taxIds);
						if (orgMap == null)
						{
							BlastResult blastResult = new BlastResult();
							blastResult.setErrorMessage("Calculation stopped midway");
							hitResults.add(blastResult);
							return hitResults;
						}
						String[] rsltFiles = fl.list();
						if (rsltFiles.length == 0)
						{
							System.out.println("No result file created ");
							success = false;
						}
						for (String rsltFile : rsltFiles)
						{
							if (rsltFile.endsWith(".out"))
							{
								String temp_path = ProjectFolder + "/" + parameters[6] + "/" + rsltFile;
								BlastResult hitResult = getHitResult(temp_path , Integer.parseInt(rsltFile.split("[.]")[0]), orgMap.get(rsltFile.split("[.]")[0]));
								if (hitResult != null) hitResults.add(hitResult);
							}
						}
						if (hitResults.size() == 0)
						{
							BlastResult blastResult = new BlastResult();
							blastResult.setErrorMessage("No results found");
							hitResults.add(blastResult);
						}
					}
					else
					{
						String projectSettingFile = null;
						String[] files = fl.list();
						for (String file : files)
						{
							if (file.endsWith(".prg"))
							{
								projectSettingFile = file;
								break;
							}
						}
						if (projectSettingFile == null)
						{
							System.out.println("ProjectSetting File is null");
						}
						else
						{
							String temp_path = ProjectFolder + "/" + parameters[6] + "/" + projectSettingFile;
							ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File(temp_path)));
							hitResults = (ArrayList<BlastResult>)inputStream.readObject();
							
							BlastResult blastResult = hitResults.get(hitResults.size() - 1);
							hitResults.clear();
							String[] taxIds = blastResult.getSelectedTaxes().split(Messages.getString("BlastServletImpl.9"));
							parameters = blastResult.getProjectSettings().split(Messages.getString("BlastServletImpl.9"));
							System.out.println("parameters are " + parameters);
							HashMap<String, String> orgMap = runBlast(path, parameters, taxIds);
							if (orgMap == null)
							{
								BlastResult bResult = new BlastResult();
								blastResult.setErrorMessage("Calculation stopped midway");
								hitResults.add(bResult);
								return hitResults;
							}
							String[] rsltFiles = fl.list();
							if (rsltFiles.length == 0)
							{
								System.out.println("No result file created ");
								success = false;
							}
							for (String rsltFile : rsltFiles)
							{
								if (rsltFile.endsWith(".out"))
								{
									temp_path = ProjectFolder + "/" + parameters[6] + "/" + rsltFile;
									BlastResult hitResult = getHitResult(temp_path , Integer.parseInt(rsltFile.split("[.]")[0]), orgMap.get(rsltFile.split("[.]")[0]));
									if (hitResult != null) hitResults.add(hitResult);
								}
							}
							if (success)
							{
								hitResults.add(blastResult);
							}
						}
					}
				}
			} 
			catch(Exception e)
			{
				e.printStackTrace();
			}
			return hitResults;
		}
	
	public boolean stopRunning(String projectFolder)
	{
		try
		{
			File fl = new File(ProjectFolder + File.separator + projectFolder + File.separator + "stop.lck");
			FileWriter fileWriter = new FileWriter(fl);
			fileWriter.write("stop");
			fileWriter.flush();
			fileWriter.close();
			return true;
		}
		catch(Exception e)
		{
			System.out.println("Could not stop running the project" + projectFolder);
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * This method creates query.fa file
	 * @param path
	 * @param success
	 * @param parameters
	 * @return
	 */
	private boolean createQueryFile(String path, boolean success,
			String[] parameters) {
		try
		{
			File queryFile = new File(path+"/resources/query.fa");
			FileWriter fw = new FileWriter(queryFile);
			fw.write(parameters[0]);
			fw.close();
		}
		catch(IOException e)
		{
			System.out.println("Could not create the query File " + path+"/resources/query.fa");
			e.printStackTrace();
			success = false;
		}
		return success;
	}

	/**
	 * This method iteratively runs blast program on each tax id selected
	 * @param path
	 * @param parameters
	 * @param taxIds
	 * @return
	 * @throws IOException
	 */
	private HashMap<String, String> runBlast(String path, String[] parameters,
			String[] taxIds) throws IOException {
		HashMap<String,String> orgMap = new HashMap<String, String>();
		for (String taxId : taxIds)
		{
			if (checkToStop(parameters)) return null;
			String[] ids = taxId.split("~");
			List<NameValuePair> lst = createQueryString(parameters, ids[1]);
			if (lst != null)
			{
				boolean rslt = antHandler.runAnt(new File(path+Messages.getString("BlastServletImpl.3")), 
						lst, Messages.getString("BlastServletImpl.4")); //$NON-NLS-1$ //$NON-NLS-2$
				if (!rslt) System.out.println("Error while running the ant task for organism " + taxId);
				else 
				{
					orgMap.put(ids[1], ids[0]);
				}
			}
			else
			{
				System.out.println("Could not create query String for tax id " + taxId);
			}
		}
		return orgMap;
	}

	private boolean checkToStop(String[] parameters) {
		File fl = new File(ProjectFolder + "/" + parameters[6]+"/"+"stop.lck");
		if (fl.exists()) return true;
		else return false;
	}

	/**
	 * This method cleans an existing workspace by deleting all the files present in it.
	 * @param parameters
	 * @param fl
	 * @return
	 */
	private boolean clearWorkspace(String[] parameters, File fl) {
		try
		{
			File[] rsltFiles = fl.listFiles();
			boolean success = true;
			for (File rsFile : rsltFiles)
			{
				System.out.println("deleting file " + rsFile.getAbsolutePath());
				success = rsFile.delete();
				if (!success)
				{
					System.out.println("Cannot delete the existing workspace " + Messages.getString("outPath_value") + parameters[6]);
					break;
				}
			}
			return success;
		}
		catch(Exception e)
		{
			System.out.println("Cannot delete the existing workspace " + Messages.getString("outPath_value") + parameters[6]);
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * This method creates a BlastResult object using the result from getMinMax method
	 * @param projectID The name of the current project folder.
	 * @param rsltFile The name of the blast file with extension.
	 * @param orgName The organism name.
	 * @return A BlastResult object
	 */
	private BlastResult getHitResult(String path , int taxid, String orgName) 
	{
		System.out.println("path " + path);
		try
		{
			// fetch out all the hits from the file
			FetchHitData data = new FetchHitData(path);
			String[] rslt = data.getNext();
			ArrayList<BlastResult> lst = new ArrayList<BlastResult>();
			while(rslt != null)
			{
				BlastResult blastResult = new BlastResult();
				blastResult.setMaxBitScoreHitIdentifier(rslt[0]).
				setMinBitScoreHitIdentifier(rslt[0]).
				setMaxEValHitIdentifier(rslt[0]).
				setMinEValHitIdentifier(rslt[0]).
				setMaxBitScore(Double.valueOf(rslt[2])).
				setMaxEVal(Double.valueOf(rslt[3]));
				lst.add(blastResult);
				rslt = data.getNext();
			}
			if (lst.size() == 0) 
			{
				BlastResult blastResult = new BlastResult();
				if (orgName == null) blastResult.setOrganismName("unknown");
				else blastResult.setOrganismName(orgName);
				blastResult.setOrganismId(taxid);
				blastResult.setMaxBitScore(0)
				.setMinBitScore(0).setMaxEVal(1001)
				.setMinEVal(1001).setMaxBitScore(0);
				lst.add(blastResult);
				return blastResult;
			}
			
			// create fetch the max and min e-val and bit scores.
			BlastResult blastResult = new BlastResult();
			if (orgName == null) blastResult.setOrganismName("unknown");
			else blastResult.setOrganismName(orgName);
			blastResult.setOrganismId(taxid);
			
			// sort it based on the bit value
			Collections.sort(lst,BlastResult.bitScoreComparator);
			// set the max and min bit scores and add the corresponding hitIdentifiers
			blastResult.setMaxBitScore(lst.get(lst.size() - 1).getMaxBitScore()).
				setMaxBitScoreHitIdentifier(lst.get(lst.size() - 1).getMaxBitScoreHitIdentifier()).
				setMinBitScore(lst.get(0).getMaxBitScore()).
				setMinBitScoreHitIdentifier(lst.get(0).getMinBitScoreHitIdentifier());
			
			// sort it based on the E values
			Collections.sort(lst,BlastResult.evalComparator);
			// set the max and min e values and add the corresponding hitIdentifiers
			blastResult.setMaxEVal(lst.get(lst.size() - 1).getMaxEVal()).
				setMaxEValHitIdentifier(lst.get(lst.size() - 1).getMaxEValHitIdentifier()).
				setMinEVal(lst.get(0).getMaxEVal()).
				setMinEValHitIdentifier(lst.get(0).getMinEValHitIdentifier());
			return blastResult;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * This method creates a query based on the input parameters, this
	 * query is in the form of NameValue pairs and is then fed to the anthandler.
	 * @param parameters a list of parameter values specifying blast program, database,
	 * 			type of query, threshhold e-value, filter, description etc.
	 * @param taxId Tax Id of the organism
	 * @return A List of NameValue pairs.
	 */
	private List<NameValuePair> createQueryString(String[] parameters, String taxId) 
	{
		File fl = new File(Messages.getString("giListPath_value")+ taxId);
		if (!fl.exists())
		{
			System.out.println("TaxID " + taxId + " is not present in the database");
			return null;
		}
		else
		{
			//query+queryType+dbType+programList+expectancy+filterList+projectFolder+newProjectCheck+blastDatabase+megablast
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			NameValuePair executableDirs = new NameValuePair(Messages.getString("executableDirs_prop"), Messages.getString("executableDirs_value")); //$NON-NLS-1$
//			NameValuePair blastExe = new NameValuePair(Messages.getString("blastExe_prop"), Messages.getString("blastp_prop")); //$NON-NLS-1$
			NameValuePair blastExe = new NameValuePair(Messages.getString("blastExe_prop"), parameters[3]); //$NON-NLS-1$
			NameValuePair dbPath = new NameValuePair(Messages.getString("dbPath_prop"), Messages.getString("dbPath_value") + parameters[8]); //$NON-NLS-1$
			NameValuePair giListPath = new NameValuePair(Messages.getString("giListPath_prop"), Messages.getString("giListPath_value")+ taxId); //$NON-NLS-1$
			NameValuePair queryPath = new NameValuePair(Messages.getString("queryPath_prop"), Messages.getString("queryPath_value")); //$NON-NLS-1$
			NameValuePair outPath = new NameValuePair(Messages.getString("outPath_prop"), Messages.getString("outPath_value") +
					parameters[6]+ "/" + taxId+".out");
			NameValuePair eVal = new NameValuePair(Messages.getString("evalue"), parameters[4]); 
			list.add(executableDirs);
			list.add(blastExe);
			list.add(dbPath);
			list.add(giListPath);
			list.add(queryPath);
			list.add(outPath);
			list.add(eVal);
			return list;
		}
	}
	
	@Override
	public String getFile(List<BlastResult> rslts) {
		BlastResult blastResult = rslts.get(rslts.size() - 1);
		if (blastResult.getDoSyn())
		{
			String saveString = saveProject(rslts);
			if (saveString.equalsIgnoreCase(""))
			{
				return saveString;
			}
		}
		
		String folderName = ProjectFolder + "/" + blastResult.getOrganismName();
		String file = null;
		File fl = new File(folderName);
		if (fl.exists() && fl.isDirectory())
		{
			String[] fileNames = fl.list();
			for (String fileName : fileNames)
			{
				if (fileName.endsWith(blastResult.getFileExtension()))
				{
					file = fileName;
					break;
				}
			}
		}
		return file;
	}
	
//	@Override
//	public List<BlastResult> getResults(String str) {
//		System.out.println(str);
//		String[] params = str.split(Messages.getString("BlastServletImpl.1"));
//		String[] parameters = params[0].split(Messages.getString("BlastServletImpl.9"));
//		ArrayList<BlastResult> hitResults = new ArrayList<BlastResult>();
//		for (int i = 0 ; i<= 0 ; i++)
//		{
//			BlastResult hitResult = getHitResult("C:/coursework/Biolab/myBlast/war/resources/598659.out" ,
//					240176, "Coprinopsis cinerea okayama7");
//			hitResult.setMaxBitScore(0.5);
//			hitResults.add(hitResult);
//			hitResult = getHitResult("C:/coursework/Biolab/myBlast/war/resources/598659.out" ,
//					598659, "Nautilia profundicola AmH");
//			hitResults.add(hitResult);
//			hitResult = getHitResult("C:/coursework/Biolab/myBlast/war/resources/598659.out" ,
//					6945, "Ixodes scapularis");
//			hitResult.setMaxBitScore(Double.valueOf("4e-70"));
//			hitResults.add(hitResult);
//			hitResult = getHitResult("C:/coursework/Biolab/myBlast/war/resources/598659.out" ,
//					233412, "Haemophilus ducreyi 35000HP");
//			hitResult.setMaxBitScore(Double.valueOf("2e-70"));
//			hitResults.add(hitResult);
//			hitResult = getHitResult("C:/coursework/Biolab/myBlast/war/resources/598659.out" ,
//					573063, "Methanocaldococcus infernus ME");
//			hitResult.setMaxBitScore(Double.valueOf("1e-70"));
//			hitResults.add(hitResult);
//			hitResult = getHitResult("C:/coursework/Biolab/myBlast/war/resources/598659.out" ,
//					228908, "Nanoarchaeum equitans Kin4-M");
//			hitResult.setMaxBitScore(Double.valueOf("2e-70"));
//			hitResults.add(hitResult);
//		}
//		
//		BlastResult hitResult = new BlastResult();
//		hitResult.setOrganismName("parit");
//		hitResult.setProjectSettings("AAAAAAAAAAAA@Protein@Protein@blastp@1@none@parit@true@false").setOrganismName("parit");
//		hitResults.add(hitResult);
//		return hitResults;
//	}
} // MyBlastServiceImpl
